
/**
 * AppleIIGo
 * Video Renderer class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 * Inspired by Jim Sather's "Understanding the Apple IIe"
 */

#include "TVRenderer.h"
#include "RGBRenderer.h"
#include "BitRenderer.h"

class VideoRenderer * VideoRenderer::getInstance(UINT32 videoRendererType,
												 Motherboard *
												 motherboard,
												 UINT32 videoType,
												 UINT32 characterSetType,
												 char * characterSetRomImage) {
	switch (videoRendererType) {
		case VIDEORENDERERTYPE_TV:
/*			return new TVRenderer(motherboard, videoType,
								  characterSetType, characterSetRomImage);
		case VIDEORENDERERTYPE_RGB:
			return new RGBRenderer(motherboard, videoType,
								   characterSetType, characterSetRomImage);
 */
			// To-Do: Remove debugging code
		default:
			bool isToDo = true;
			return new BitRenderer(motherboard, videoType,
								   characterSetType, characterSetRomImage);
	}
}

VideoRenderer::VideoRenderer(Motherboard * motherboard,
							 UINT32 videoType,
							 UINT32 characterSetType,
							 char * characterSetRomImage) :
Device(motherboard, "VideoRenderer") {
	isAppleIIVideo = (videoType == VIDEOTYPE_II);
	isAppleIIeEnhancedVideo =
		(videoType != VIDEOTYPE_II) && (videoType != VIDEOTYPE_IIE);
	
	videoMaps = new VideoMaps(this);
	videoModeToRenderMap = videoMaps->getVideoModeToRenderMap();
	videoModeToAddressMap = videoMaps->getVideoModeToAddressMap();
	videoModeToIsVideoModifiedMap =
		videoMaps->getVideoModeToIsVideoModifiedMap();
	
	initVideoMode();
	initCharacterSet(videoType, characterSetType, characterSetRomImage);
	setIsFramebufferUpdated(false);
}

void VideoRenderer::onDevicesConnected() {
	mmu = motherboard->getMmu();
	timingGenerator = motherboard->getTimingGenerator();
	
	setVideoMode(videoMode);
}

void VideoRenderer::onMemoryAllocated() {
	shadowMemory = motherboard->getMemory()->getShadowMemory();
}

void VideoRenderer::onResetAsserted(bool isRestart) {
	if (isAppleIIVideo && !isRestart)
		return;
	
	updateVideo();
	if (isRestart) {
		setText(false);
		setMixed(false);
	}
	setPage2(false);
	setHires(false);
	
	set80Col(false);
	set80Store(false);
	setDHires(isAppleIIeEnhancedVideo ? true : false);
	setAltChar(false);
	
	setMonochromeRegister(0);
	setScreenColorRegister(0xf0);
	setBorderColorRegister(0xf);
	setNewVideoRegister(0);
	setLanguageRegister(0);
	setCharacterSetReadAddress(0);
	
	setVideoterm(false);
}

void VideoRenderer::initVideoMode() {
	videoMode = 0;
	frameUpdateScanlineCount = 0;
}

void VideoRenderer::setVideoMode(UINT32 value) {
	videoMode = value;
	
	renderMap = videoModeToRenderMap[videoMode];
	addressMap = videoModeToAddressMap[videoMode];
	mmu->setIsVideoModifiedMap(videoModeToIsVideoModifiedMap[videoMode]);
}

void VideoRenderer::setText(bool value) {
	if (isText() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_TEXT, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::isText() {
	return getBit(videoMode, VIDEOMODE_TEXT);
}

void VideoRenderer::setMixed(bool value) {
	if (isMixed() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_MIXED, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::isMixed() {
	return getBit(videoMode, VIDEOMODE_MIXED);
}

void VideoRenderer::setPage2(bool value) {
	if (isPage2() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_PAGE2, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::isPage2() {
	return getBit(videoMode, VIDEOMODE_PAGE2);
}

void VideoRenderer::setHires(bool value) {
	if (isHires() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_HIRES, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::isHires() {
	return getBit(videoMode, VIDEOMODE_HIRES);
}

void VideoRenderer::set80Col(bool value) {
	if (is80Col() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_80COL, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::is80Col() {
	return getBit(videoMode, VIDEOMODE_80COL);
}

void VideoRenderer::set80Store(bool value) {
	if (is80Store() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_80STORE, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::is80Store() {
	return getBit(videoMode, VIDEOMODE_80STORE);
}

void VideoRenderer::setDHires(bool value) {
	if (isDHires() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_DHIRES, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::isDHires() {
	return getBit(videoMode, VIDEOMODE_DHIRES);
}

void VideoRenderer::setAltChar(bool value) {
	if (isAltChar() != value) {
		updateVideo();
		setBit(characterSetMapFlags, CHARACTERSETMAP_ALT, value);
	}
}

bool VideoRenderer::isAltChar() {
	return getBit(characterSetMapFlags, CHARACTERSETMAP_ALT);
}

void VideoRenderer::setMonochromeRegister(UINT8 value) {
	monochromeRegister = value;
}

UINT8 VideoRenderer::getMonochromeRegister() {
	return monochromeRegister;
}

void VideoRenderer::setScreenColorRegister(UINT8 value) {
	if (screenColorRegister != value) {
		updateVideo();
		screenColorRegister = value;
	}
}

UINT8 VideoRenderer::getScreenColorRegister() {
	return screenColorRegister;
}

void VideoRenderer::setBorderColorRegister(UINT8 value) {
	if (borderColorRegister != value) {
		updateVideo();
		borderColorRegister = value;
	}
}

UINT8 VideoRenderer::getBorderColorRegister() {
	return borderColorRegister;
}

void VideoRenderer::setNewVideoRegister(UINT8 value) {
	if (newVideoRegister != value) {
		updateVideo();
		newVideoRegister = value;
		setBit(videoMode, VIDEOMODE_SHIRES,
			   getBit(value, NEWVIDEO_SHIRES));
		setBit(videoMode, VIDEOMODE_DHIRESMONOCHROME,
			   getBit(value, NEWVIDEO_DHIRESMONOCHROME));
		setVideoMode(videoMode);
	}
}

UINT8 VideoRenderer::getNewVideoRegister() {
	return newVideoRegister;
}

void VideoRenderer::setLanguageRegister(UINT8 value) {
	if (languageRegister != value) {
		updateVideo();
		languageRegister = value;
		if (getBit(value, LANGUAGE_SECONDARY))
			value = 0;
		setCharacterSetLanguage(CHARACTERSETGROUP_DEFAULT, value >> 5);
	}
}

UINT8 VideoRenderer::getLanguageRegister() {
	return languageRegister;
}

void VideoRenderer::setCharacterSetReadAddress(UINT8 value) {
	characterSetReadAddress = value;
}

UINT8 VideoRenderer::getCharacterSetReadAddress() {
	return characterSetReadAddress;
}

bool VideoRenderer::isSHires() {
	return getBit(videoMode, VIDEOMODE_SHIRES);
}

void VideoRenderer::setVideoterm(bool value) {
	if (isVideoterm() != value) {
		updateVideo();
		setBit(videoMode, VIDEOMODE_VIDEOTERM, value);
		setVideoMode(videoMode);
	}
}

bool VideoRenderer::isVideoterm() {
	return getBit(videoMode, VIDEOMODE_VIDEOTERM);
}

void VideoRenderer::initCharacterSet(UINT32 videoType,
									 UINT32 characterSetType,
									 char * characterSetRomImage) {
	characterSetLoader = new CharacterSetLoader(videoType,
												motherboard->getResources());
	characterSetLoader->loadCharacterSet(characterSetType,
										 characterSetRomImage);
	characterSetMap = characterSetLoader->getCharacterSetMap();
	characterSetMapFlags = 0;
	
	characterSetLanguage[CHARACTERSETGROUP_DEFAULT] =
		CHARACTERSETLANGUAGE_DEFAULT;
	characterSetLanguage[CHARACTERSETGROUP_USER] =
		CHARACTERSETLANGUAGE_DEFAULT;
	characterSetGroupIndex = CHARACTERSETGROUP_DEFAULT;
	
	characterSetFlashFrameIndex = 0;
}

bool VideoRenderer::loadCharacterSet(UINT32 characterSetType,
									 char * characterSetRomImage) {
	return characterSetLoader->loadCharacterSet(characterSetType,
												characterSetRomImage);
}

void VideoRenderer::toggleCharacterSetFlash() {
	updateVideo();
	setBit(characterSetMapFlags, CHARACTERSETMAP_FLASH,
		   !getBit(characterSetMapFlags, CHARACTERSETMAP_FLASH));
}

void VideoRenderer::setCharacterSetLanguage(UINT32 groupIndex,
											UINT32 languageIndex) {
	if ((groupIndex < CHARACTERSETGROUP_END) &&
		(languageIndex < CHARACTERSETLANGUAGE_END)) {
		if (characterSetLanguage[groupIndex] != languageIndex) {
			characterSetLanguage[groupIndex] = languageIndex;
			
			if (characterSetGroupIndex == groupIndex) {
				updateVideo();
				characterSetLoader->setLanguage(languageIndex);
				motherboard->getKeyboard()->setLanguage(languageIndex);
			}
		}
	}
}

void VideoRenderer::setCharacterSetGroup(UINT32 groupIndex) {
	if (groupIndex < CHARACTERSETGROUP_END) {
		if (characterSetGroupIndex != groupIndex) {
			characterSetGroupIndex = groupIndex;
			updateVideo();
			characterSetLoader->setLanguage(
				characterSetLanguage[groupIndex]);
			motherboard->getKeyboard()->setLanguage(
				characterSetLanguage[groupIndex]);
		}
	}
}

UINT32 VideoRenderer::getVideoScannerAddress(UINT32 verticalCount,
											 UINT32 horizontalCount,
											 UINT32 videoMode) {
	bool isMixedTextAccess = getBit(videoMode, VIDEOMODE_MIXED) && 
		((verticalCount & 0xa0) == 0xa0);
	bool isHiresAccess = getBit(videoMode, VIDEOMODE_HIRES) &&
		!(getBit(videoMode, VIDEOMODE_TEXT) || isMixedTextAccess);
	bool isPage2Access = getBit(videoMode, VIDEOMODE_PAGE2) &&
		!getBit(videoMode, VIDEOMODE_80STORE);
	
	UINT32 address = horizontalCount & 0x7;
	UINT32 h5h4h3 = horizontalCount & 0x38;
	UINT32 v4v3 = verticalCount & 0xc0;
	UINT32 v4v3v4v3 = (v4v3 >> 3) | (v4v3 >> 1);
	address |= ((0x68 + h5h4h3 + v4v3v4v3) & 0x78);
	address |= (verticalCount & 0x38) << 4;
	if (isHiresAccess) {
		address |= (verticalCount & 0x7) << 10;
		address += isPage2Access ? HIRESPAGE2_ADDRESS : HIRESPAGE1_ADDRESS;
	} else {
		address += isPage2Access ? TEXTPAGE2_ADDRESS : TEXTPAGE1_ADDRESS;
		if (isAppleIIVideo) {
			bool isHbl = horizontalCount < HORIZ_ACTIVEVIDEO;
			address |= isHbl ? 0x1000 : 0x0000;
		}
	}
	
	return address;
}

UINT8 VideoRenderer::readVideoScanner(UINT32 verticalCount,
									  UINT32 horizontalCount) {
	return shadowMemory[getVideoScannerAddress(verticalCount, horizontalCount,
											   videoMode)];
}

bool * VideoRenderer::getIsVideoModified() {
	return isVideoModified;
}

void VideoRenderer::copyCharacter(struct copyDataType * copyData,
								  UINT32 value) {
	value &= 0x7f;
	if (!(value & 0x60))
		value |= 0x40;
	
	copyData->buffer[copyData->bufferIndex++] = value;
	
	if ((value != ' ') && (value != 0x7f))
		copyData->bufferTrimIndex = copyData->bufferIndex;
}

void VideoRenderer::copyLine(struct copyDataType * copyData) {
	if (copyData->bufferLineIndex != copyData->bufferTrimIndex)
		copyData->bufferEndIndex = copyData->bufferTrimIndex + 1;
	
	copyData->bufferIndex = copyData->bufferTrimIndex;
	copyData->buffer[copyData->bufferIndex++] = '\n';
	copyData->bufferLineIndex =
		copyData->bufferTrimIndex = copyData->bufferIndex;
}

void VideoRenderer::copy(char * buffer, UINT32 bufferSize) {
	if (!isCopyAvailable())
		return;
	
	struct copyDataType copyData = {"", 0, 0, 0};
	
	if (isVideoterm()) {
		for (UINT32 rowIndex = 0; rowIndex < VIDEOTERM_ROWNUM; rowIndex++) {
			UINT32 rowAddress = addressMap[SCANLINE_VIDEOTERM +
										   rowIndex * VIDEOTERM_CHARHEIGHT];
			for (UINT32 columnIndex = 0; columnIndex < VIDEOTERM_PITCH;
				 columnIndex++)
				copyCharacter(&copyData, shadowMemory[rowAddress +
													  columnIndex]);
			copyLine(&copyData);
		}
	} else {
		for (UINT32 rowIndex = !isText() && isMixed() ? APPLEII_ROWMIXED : 0;
			 rowIndex < VIDEOTERM_ROWNUM; rowIndex++) {
			UINT32 rowAddress = addressMap[SCANLINE_APPLEII +
										   rowIndex * APPLEII_CHARHEIGHT];
			for (UINT32 columnIndex = 0; columnIndex < APPLEII_PITCH;
				 columnIndex++) {
				if (is80Col())
					copyCharacter(&copyData, shadowMemory[BANK_BYTENUM +
														  rowAddress +
														  columnIndex]);
				copyCharacter(&copyData, shadowMemory[rowAddress +
													  columnIndex]);
			}
			copyLine(&copyData);
		}
	}
	
	copyData.buffer[copyData.bufferEndIndex] = '\0';
	char * copyBuffer = copyData.buffer;
	for (UINT32 index = 0; (index < bufferSize) && copyBuffer[index]; index++) {
		buffer[index] = copyBuffer[index];
		buffer[index + 1] = '\0';
	}
}

bool VideoRenderer::isCopyAvailable() {
	return isVideoterm() || (!isSHires() && (isText() || isMixed()));
}

void VideoRenderer::setIsFramebufferUpdated(bool value) {
	isFramebufferUpdatedValue = value;
}

void VideoRenderer::setFramebuffer(UINT32 * framebuffer,
								   UINT32 framebufferPitch) {
	this->framebuffer = framebuffer;
	this->framebufferPitch = framebufferPitch / sizeof(UINT32);
}

bool VideoRenderer::isFramebufferUpdated() {
	return isFramebufferUpdatedValue;
}

void VideoRenderer::updateVideo() {
	isVideoModified[ISVIDEOMODIFIED_FRAME] = true;
	
	UINT32 scanlineIndex = timingGenerator->getScanlineIndex();
	if (scanlineIndex == ISVIDEOMODIFIED_INVISIBLE)
		return;
	SINT32 horizEnd = timingGenerator->getHorizontalCount() - HORIZ_ACTIVEVIDEO;
	if (horizEnd >= 0)
		renderScanline(scanlineIndex, horizEnd);
}

void VideoRenderer::onNextFrame() {
	// TO-DO: ADD VIDEOTERM HERE
	bool isToDo = true;
	if (isVideoterm()) {
		return;
	}
	
	if (isSHires() || (!isText() && !isMixed() && isHires()))
		return;
	
	characterSetFlashFrameIndex++;
	if (characterSetFlashFrameIndex >= CHARACTERSET_FLASH_FRAMENUM) {
		characterSetFlashFrameIndex = 0;
		toggleCharacterSetFlash();
	}
	
	UINT32 textlineStart = (!isText() && isMixed() && isHires()) ? 20 : 0;	
	for (UINT32 index = textlineStart; index < APPLEII_ROWNUM; index++) {
		if (isVideoModified[ISVIDEOMODIFIED_TEXTLINE + index]) {
			isVideoModified[ISVIDEOMODIFIED_TEXTLINE + index] = false;
			
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 0] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 1] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 2] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 3] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 4] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 5] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 6] = true;
			isVideoModified[SCANLINE_APPLEII +
							index * APPLEII_CHARHEIGHT + 7] = true;
		}
	}
}

void VideoRenderer::onScanline(UINT32 scanlineIndex) {
	bool isToDo = true;
	// To-Do: Fix shadow address
	//	if (scanlineInterrupt->isEnabled() && isSHires() && shadowMemory[0])
	//		scanlineInterrupt->assert();
	// if docint
	// assertint
	if (scanlineIndex >= SCANLINE_END)
		return;
	if (isVideoModified[ISVIDEOMODIFIED_FRAME]) {
		isVideoModified[ISVIDEOMODIFIED_FRAME] = false;
		frameUpdateScanlineCount = SCANLINE_END + 1;
	}
	if (frameUpdateScanlineCount) {
		frameUpdateScanlineCount--;
		isVideoModified[scanlineIndex] = false;
		renderScanline(scanlineIndex);
	} else if (isVideoModified[scanlineIndex]) {
		isVideoModified[scanlineIndex] = false;
		renderScanline(scanlineIndex);
	}
}

void VideoRenderer::renderScanline(UINT32 scanlineIndex, UINT32 horizEnd) {
}

void VideoRenderer::renderScanline(UINT32 scanlineIndex) {
}
